import { ElementWrapper, TextNodeWrapper } from './components'
import { setCurrentVdom } from '../hook/index'
import { isFunction, isNumber, isObject, isString } from '../types';
import { eventNames, normalizeEventName } from '../events'

let root;
let commitQueue = []

export const render = (vnode, parentDom) => {
  commitQueue = []
  if (!root) {
    root = vnode
  }

  if (root && !parentDom) {
    parentDom = root._parent
  }
  vnode._parent = parentDom

  const oldVNode = parentDom._oldVNode;
  parentDom._oldVNode = vnode;

  diff(parentDom, vnode, oldVNode)
  flushCommitQueue(commitQueue)
}

export const diff = (parentDom, vnode, oldVNode) => {
  const type = vnode.type;

  vnode._vdom = oldVNode?._vdom || {}
  let currentVdom = setCurrentVdom(vnode._vdom)
  currentVdom._state = currentVdom._state || []
  currentVdom._index = 0

  // old dom
  const oldVDom = vnode._vdom.value
  // new dom
  const element = vnode._vdom.value = createVdom(type, vnode)

  const oldChildrenNode = oldVNode?._children || []
  vnode._children = []
  if (!(element instanceof ElementWrapper)) {
    // record
    vnode._children.push(element)
    diff(parentDom, element, oldChildrenNode[0])
  } else {
    // children
    const children = vnode.props.children || []
    children.forEach((child, index) => {
      if (isString(child) || isNumber(child)) {
        child = new TextNodeWrapper(child)
        
        commitQueue.push(function task() {
          child.mountTo(element)
        })
      } else if (isObject(child) && child.type) {
        // is vnode
        vnode._children.push(child)
        diff(element, child, oldChildrenNode[index])
      }
    })
    vnode._parent = parentDom
    commitQueue.push(function task() {
      if (element) {
        if (oldVNode?._parent === parentDom && oldVDom) {
          // replace
          parentDom.replaceChild(element.root, oldVDom.root)
        } else {
          element.mountTo(parentDom)
        }
      }
    })
  }
}

export function createVdom(type, vnode) {
  const newProps = vnode.props
  let element;
  if (isFunction(type)) {
    return type(vnode.props)
  } else {
    element = new ElementWrapper(type)
  }

  // setAttribute
  for (const name in newProps) {
    if (name === 'children') continue;
    if (eventNames.some(e => e === name)) {
      element.addEventListener(normalizeEventName(name), newProps[name])
    } else {
      element.setAttribute(name, newProps[name])
    }
  }
  vnode.key && element.setAttribute('key', vnode.key)

  return element
}

export function flushCommitQueue(queue) {
  setTimeout(() => {
    for (let scheduler of queue) {
      isFunction(scheduler) && scheduler()
    }
  }, 0)
}

export const update = () => {
  render(root)
}
